#include <audio/MIDI/MIDI.h>
#include <system/Log.h>

namespace ne
{
    MIDIPlayer::MIDIPlayer()
        : mpOutDevice(0),
          mpTimer(0),
          mPlayedTime(0),
          mPlayedTicks(0),
          mIsPlaying(false),
          mpSequence(0)
    {
#if NE_PLATFORM == NE_PLATFORM_MACOSX
        mpOutDevice = new MIDIMusicDeviceMacOSX();
#elif NE_PLATFORM == NE_PLATFORM_WINDOWS
        mpOutDevice = new MIDIMusicDeviceWin32();
#endif
        mpOutDevice->open();

        mpTimer = new Timer();
        mpTimer->reset();
    }

    void MIDIPlayer::setSequence(const MIDISequencePtr &pSequence)
    {
        mpSequence = pSequence;
        reset();
    }

    MIDISequencePtr MIDIPlayer::getSequence() const
    {
        return mpSequence;
    }

    double MIDIPlayer::getPlayedTime() const
    {
        return mPlayedTime;
    }

    double MIDIPlayer::getPlayedTicks() const
    {
        return mPlayedTicks;
    }

    bool MIDIPlayer::isPlaying() const
    {
        return mIsPlaying;
    }

    bool MIDIPlayer::play()
    {
        if (mpSequence.isNull()) return false;

        mIsPlaying = true;

        return true;
    }

    bool MIDIPlayer::stop()
    {
        sendEvent(MIDI_CONTROLLER, 0, MIDI_MODE_ALL_NOTES_OFF, 0);
        mIsPlaying = false;

        return true;
    }

    bool MIDIPlayer::reset()
    {
        stop();

        mpTimer->reset();
        mPlayedTime = 0;
        mPlayedTicks = 0;

        mEventListIterators.clear();

        if (mpSequence.isNull()) return false;

        size_t numTracks = mpSequence->getNumTracks();
        for (size_t i=0; i<numTracks; ++i)
        {
            MIDITrack *pTrack = mpSequence->getTrack(i);
            mEventListIterators.push_back(pTrack->begin());
        }

        apply(0);

        return true;
    }

    bool MIDIPlayer::tick()
    {
        double interval = 1000 * mpTimer->getIntervalMilliseconds();

        if (!isPlaying()) return false;

        if (mpSequence.isNull()) return false;

        mPlayedTime += interval;
        mPlayedTicks = mpSequence->time2Ticks(mPlayedTime);

        if (!apply(mPlayedTicks))
        {
            stop();
        }

        return true;
    }

    bool MIDIPlayer::seek(const double ticks)
    {
        if (mpSequence.isNull()) return false;

        if (ticks <= mPlayedTicks)
        {
            reset();
        }

        size_t numTracks = mpSequence->getNumTracks();
        if (mEventListIterators.size() != numTracks) return false;

        for (size_t i=0; i<numTracks; ++i)
        {
            MIDITrack *pTrack = mpSequence->getTrack(i);
            while (mEventListIterators[i] != pTrack->end())
            {
                MIDIEvent *pEvent = *mEventListIterators[i];
                if (pEvent->absoluteTicks < ticks)
                {
                    ++mEventListIterators[i];
                }
                else
                {
                    break;
                }
            }
        }

        mPlayedTicks = ticks;
        mPlayedTime = mpSequence->ticks2Time(mPlayedTicks);
        return true;
    }
    
    bool MIDIPlayer::apply(const double ticks)
    {
        if (mpSequence.isNull()) return false;

        size_t numTracks = mpSequence->getNumTracks();
        if (mEventListIterators.size() != numTracks) return false;

        bool hasEvent = false;
        while (true)
        {
            // TODO: Find minTicks event
            hasEvent = false;
            size_t minTicksIndex = numTracks;
            double minTicks = LLONG_MAX;
            for (size_t i=0; i<numTracks; ++i)
            {
                MIDITrack *pTrack = mpSequence->getTrack(i);
                if (mEventListIterators[i] != pTrack->end())
                {
                    MIDIEvent *pEvent = *mEventListIterators[i];
                    hasEvent = true;
                    if (pEvent->absoluteTicks < minTicks)
                    {
                        minTicksIndex = i;
                        minTicks = pEvent->absoluteTicks;
                    }
                }
            }

            if (minTicksIndex < numTracks && minTicks <= ticks)
            {
                MIDITrack *pTrack = mpSequence->getTrack(minTicksIndex);
                if (mEventListIterators[minTicksIndex] != pTrack->end())
                {
                    MIDIEvent *pEvent = *mEventListIterators[minTicksIndex];
                    applyEvent(pEvent);
                    ++mEventListIterators[minTicksIndex];
                }
            }
            else
            {
                break;
            }
        }
        return hasEvent;
    }

    bool MIDIPlayer::applyEvent(MIDIEvent *pEvent)
    {
        if (pEvent)
        {
            byte event = pEvent->getType();
            if (event < MIDI_SYSTEM)
            {
                MIDIChannelEvent *pChannelEvent = dynamic_cast<MIDIChannelEvent*>(pEvent);
                if (pChannelEvent)
                {
                    return sendEvent(event, pChannelEvent->channel, pChannelEvent->parameter1, pChannelEvent->parameter2);
                }
            }
            else if (event == MIDI_META)
            {
            }
            else
            {
            }
        }
        return false;
    }

    bool MIDIPlayer::sendEvent(const byte event, const byte channel, const byte parameter1, const byte parameter2)
    {
        return mpOutDevice->sendEvent(event | (channel & MIDI_CHANNEL_MASK), parameter1, parameter2);
    }

    bool MIDIPlayer::sendSysExEvent(const byte *buffer, const uint32_t length)
    {
        return mpOutDevice->sendSysExEvent(buffer, length);
    }

    MIDIPlayer::~MIDIPlayer()
    {
        mpOutDevice->close();
        delete mpOutDevice;
    }
}
